package com.example.demo.demos.utils;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.Date;

// 自定义异常类
class ApiException extends RuntimeException {
    public ApiException(String message) {
        super(message);
    }
}

// 定义异常枚举类
enum ExceptionEnums {
    SNOWFLAKE_OUT_OF_RANGE("机器ID或服务ID超出最大范围"),
    SNOWFLAKE_TIME_BACK("出现时间回拨");

    private final String message;

    ExceptionEnums(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }
}

@Component
public class SnowflakeIdUtil {

    /**
     * 41个bit位存储时间戳，从0开始计算，最多可以存储69.7年。
     * 那么如果默认使用，从1970年到现在，最多可以用到2039年左右。
     * 按照从2024-10-8号开始计算，存储41个bit为，这样最多可以使用到2124年不到~~
     */
    private long timeStart = 1728316800000L;

    /**
     * 机器id，默认值设置为0
     */
    @Value("${snowflake.machineId:0}")
    private long machineId;

    /**
     * 服务id，默认值设置为0
     */
    @Value("${snowflake.serviceId:0}")
    private long serviceId;

    /**
     * 序列
     */
    private long sequence;


    /**
     * 机器id占用的bit位数
     */
    private long machineIdBits = 5L;

    /**
     * 服务id占用的bit位数
     */
    private long serviceIdBits = 5L;

    /**
     * 序列占用的bit位数
     */
    private long sequenceBits = 12L;

    /**
     * 计算机器id的最大值
     */
    private long maxMachineId = -1 ^ (-1 << machineIdBits);

    /**
     * 计算服务id的最大值
     */
    private long maxServiceId = -1 ^ (-1 << serviceIdBits);

    @PostConstruct
    public void init() {

        if (machineId > maxMachineId || serviceId > maxServiceId) {
            System.out.println("机器ID或服务ID超过最大范围值！！");
            throw new ApiException(ExceptionEnums.SNOWFLAKE_OUT_OF_RANGE.getMessage());
        }
    }

    /**
     * 服务id需要位移的位数
     */
    private long serviceIdShift = sequenceBits;

    /**
     * 机器id需要位移的位数
     */
    private long machineIdShift = sequenceBits + serviceIdBits;

    /**
     * 时间戳需要位移的位数
     */
    private long timestampShift = sequenceBits + serviceIdBits + machineIdBits;

    /**
     *  序列的最大值
     */
    private long maxSequenceId = -1 ^ (-1 << sequenceBits);

    /**
     * 记录最近一次获取id的时间
     */
    private long lastTimestamp = -1;


    /**
     *  获取系统时间毫秒值
     * @return
     */
    private long timeGen() {
        return System.currentTimeMillis();
    }


    /**
     * 多个系统调用该方法，所以该方法需要加锁
     *
     * @return
     */
    public synchronized long nextId() {
        //1、 拿到当前系统时间的毫秒值
        long timestamp = timeGen();
        // 避免时间回拨造成出现重复的id
        if (timestamp < lastTimestamp) {
            // 说明出现了时间回拨
            System.out.println("当前服务出现时间回拨！！！");
            throw new ApiException(ExceptionEnums.SNOWFLAKE_TIME_BACK.getMessage());
        }

        //2、 判断当前生成id的时间和上一次生成的时间
        if (timestamp == lastTimestamp) {
            // 同一毫秒值生成id
            sequence = (sequence + 1) & maxSequenceId;
            // 0000 10100000 :sequence
            // 1111 11111111 :maxSequenceId
            if (sequence == 0) {
                // 进到这个if，说明已经超出了sequence序列的最大取值范围
                // 需要等到下一个毫秒再做回来生成具体的值
                timestamp = timeGen();
                while (timestamp <= lastTimestamp) {
                    // 时间还没动。
                    timestamp = timeGen();
                }
            }
        } else {
            // 另一个时间点生成id
            sequence = 0;
        }
        //3、重新给lastTimestamp复制
        lastTimestamp = timestamp;

        //4、计算id，将几位值拼接起来。  41bit位的时间，5位的机器，5位的服务 ，12位的序列
        return ((timestamp - timeStart) << timestampShift) |
                (machineId << machineIdShift) |
                (serviceId << serviceIdShift) |
                sequence &
                        Long.MAX_VALUE;//避免符号位被占用
    }


    public static void main(String[] args) {

        //计算当前时间作为其实时间的毫秒值
        LocalDate localDate = LocalDate.of(2024, 10, 8);
        Instant instant = localDate.atStartOfDay(ZoneId.systemDefault()).toInstant();
        System.out.println(instant.toEpochMilli());

        System.out.println(new Date(1728316800000L));

        SnowflakeIdUtil snowflakeIdUtil = new SnowflakeIdUtil();
        // 调用 nextId 方法生成雪花 ID
        long id = snowflakeIdUtil.nextId();
        System.out.println("生成的雪花 ID: " + id);
    }
}